home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Chip: Internet
/
Chip Internet.iso
/
viewer
/
sox7dos
/
sox.c
< prev
next >
Wrap
Text File
|
1993-02-11
|
17KB
|
754 lines
/*
* July 5, 1991
* Copyright 1991 Lance Norskog And Sundry Contributors
* This source code is freely redistributable and may be used for
* any purpose. This copyright notice must be maintained.
* Lance Norskog And Sundry Contributors are not responsible for
* the consequences of using this software.
*/
#include "st.h"
#if defined(unix) || defined(AMIGA)
#include <sys/types.h>
#include <sys/stat.h>
#endif
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <ctype.h>
#include <string.h>
#ifdef VMS
#include <errno.h>
#include <perror.h>
#define LASTCHAR ']'
#else
#include <errno.h>
#define LASTCHAR '/'
#endif
/*
* SOX main program.
*
* Rewrite for new nicer option syntax. July 13, 1991.
* Rewrite for separate effects library. Sep. 15, 1991.
*/
#ifdef AMIGA
/* This is the Amiga version string */
char amiversion[AmiVerSize]=AmiVerChars;
#endif /* AMIGA */
EXPORT float volume = 1.0; /* expansion coefficient */
int dovolume = 0;
int clipped = 0; /* Volume change clipping errors */
EXPORT float amplitude = 1.0; /* Largest sample so far */
EXPORT int writing = 0; /* are we writing to a file? */
/* export flags */
EXPORT int verbose = 0; /* be noisy on stderr */
EXPORT int summary = 0; /* just print summary of information */
long ibuf[BUFSIZ]; /* Intermediate processing buffer */
long obuf[BUFSIZ]; /* Intermediate processing buffer */
long volumechange();
#ifdef DOS
char writebuf[BUFSIZ]; /* output write buffer */
#endif
void gettype(), geteffect(), checkeffect();
EXPORT struct soundstream informat, outformat;
char *myname;
ft_t ft;
struct effect eff;
char *ifile, *ofile, *itype, *otype;
IMPORT char *optarg;
IMPORT int optind;
main(n, args)
int n;
char **args;
{
myname = args[0];
init();
ifile = ofile = NULL;
/* Get input format options */
ft = &informat;
doopts(n, args);
/* Get input file */
if (optind >= n)
#ifndef VMS
usage("No input file?");
#else
/* I think this is the VMS patch, but am not certain */
fail("Input file required");
#endif
ifile = args[optind];
if (! strcmp(ifile, "-"))
ft->fp = stdin;
else if ((ft->fp = fopen(ifile, READBINARY)) == NULL)
fail("Can't open input file '%s': %s",
ifile, strerror(errno));
ft->filename = ifile;
optind++;
/* Let -e allow no output file, just do an effect */
if (optind < n) {
if (strcmp(args[optind], "-e")) {
/* Get output format options */
ft = &outformat;
doopts(n, args);
/* Get output file */
if (optind >= n)
usage("No output file?");
ofile = args[optind];
ft->filename = ofile;
/*
* There are two choices here:
* 1) stomp the old file - normal shell "> file" behavior
* 2) fail if the old file already exists - csh mode
*/
if (! strcmp(ofile, "-"))
ft->fp = stdout;
else {
#ifdef unix
/*
* Remove old file if it's a text file, but
* preserve Unix /dev/sound files. I'm not sure
* this needs to be here, but it's not hurting
* anything.
*/
if ((ft->fp = fopen(ofile, WRITEBINARY)) &&
(filetype(fileno(ft->fp)) == S_IFREG)) {
fclose(ft->fp);
unlink(ofile);
creat(ofile, 0666);
ft->fp = fopen(ofile, WRITEBINARY);
}
#else
ft->fp = fopen(ofile, WRITEBINARY);
#endif
if (ft->fp == NULL)
fail("Can't open output file '%s': %s",
ofile, strerror(errno));
#ifdef DOS
if (setvbuf (ft->fp,writebuf,_IOFBF,sizeof(writebuf)))
fail("Can't set write buffer");
#endif
}
writing = 1;
}
optind++;
}
/* ??? */
/*
if ((optind < n) && !writing && !eff.name)
fail("Can't do an effect without an output file!");
*/
/* Get effect name */
if (optind < n) {
eff.name = args[optind];
optind++;
geteffect(&eff);
(* eff.h->getopts)(&eff, n - optind, &args[optind]);
}
/*
* If we haven't specifically set an output file
* don't write a file; we could be doing a summary
* or a format check.
*/
/*
if (! ofile)
usage("Must be given an output file name");
*/
if (! ofile)
writing = 0;
/* Check global arguments */
if (volume <= 0.0)
fail("Volume must be greater than 0.0");
#if defined(unix) || defined(AMIGA)
informat.seekable = (filetype(fileno(informat.fp)) == S_IFREG);
outformat.seekable = (filetype(fileno(outformat.fp)) == S_IFREG);
#else
#if defined(DOS)
informat.seekable = 1;
outformat.seekable = 1;
#else
informat.seekable = 0;
outformat.seekable = 0;
#endif
#endif
/* If file types have not been set with -t, set from file names. */
if (! informat.filetype) {
if (informat.filetype = strrchr(ifile, LASTCHAR))
informat.filetype++;
else
informat.filetype = ifile;
if (informat.filetype = strrchr(informat.filetype, '.'))
informat.filetype++;
else /* Default to "auto" */
informat.filetype = "auto";
}
if (writing && ! outformat.filetype) {
if (outformat.filetype = strrchr(ofile, LASTCHAR))
outformat.filetype++;
else
outformat.filetype = ofile;
if (outformat.filetype = strrchr(outformat.filetype, '.'))
outformat.filetype++;
}
/* Default the input comment to the filename.
* The output comment will be assigned when the informat
* structure is copied to the outformat.
*/
informat.comment = informat.filename;
process();
statistics();
exit(0);
}
doopts(n, args)
int n;
char **args;
{
int c;
char *str;
while ((c = getopt(n, args, "r:v:t:c:suUAbwlfdDxSV")) != -1) {
switch(c) {
case 't':
if (! ft) usage("-t");
ft->filetype = optarg;
if (ft->filetype[0] == '.')
ft->filetype++;
break;
case 'r':
if (! ft) usage("-r");
str = optarg;
if ((! sscanf(str, "%lu", &ft->info.rate)) ||
(ft->info.rate <= 0))
fail("-r must be given a positive integer");
break;
case 'v':
if (! ft) usage("-v");
str = optarg;
if ((! sscanf(str, "%e", &volume)) ||
(volume <= 0))
fail("Volume value '%s' is not a number",
optarg);
dovolume = 1;
break;
case 'c':
if (! ft) usage("-c");
str = optarg;
if (! sscanf(str, "%d", &ft->info.channels))
fail("-c must be given a number");
break;
case 'b':
if (! ft) usage("-b");
ft->info.size = BYTE;
break;
case 'w':
if (! ft) usage("-w");
ft->info.size = WORD;
break;
case 'l':
if (! ft) usage("-l");
ft->info.size = LONG;
break;
case 'f':
if (! ft) usage("-f");
ft->info.size = FLOAT;
break;
case 'd':
if (! ft) usage("-d");
ft->info.size = DOUBLE;
break;
case 'D':
if (! ft) usage("-D");
ft->info.size = IEEE;
break;
case 's':
if (! ft) usage("-s");
ft->info.style = SIGN2;
break;
case 'u':
if (! ft) usage("-u");
ft->info.style = UNSIGNED;
break;
case 'U':
if (! ft) usage("-U");
ft->info.style = ULAW;
break;
case 'A':
if (! ft) usage("-A");
ft->info.style = ALAW;
break;
case 'x':
if (! ft) usage("-x");
ft->swap = 1;
break;
/* stat effect does this ?
case 'S':
summary = 1;
break;
*/
case 'V':
verbose = 1;
break;
}
}
}
init() {
/* init files */
informat.info.rate = outformat.info.rate = 0;
informat.info.size = outformat.info.size = -1;
informat.info.style = outformat.info.style = -1;
informat.info.channels = outformat.info.channels = -1;
informat.comment = outformat.comment = NULL;
informat.swap = 0;
informat.filetype = outformat.filetype = (char *) 0;
informat.fp = stdin;
outformat.fp = stdout;
informat.filename = "input";
outformat.filename = "output";
}
/*
* Process input file -> effect -> output file
* one buffer at a time
*/
process() {
long isamp, osamp, istart;
long i, idone, odone;
gettype(&informat);
if (writing)
gettype(&outformat);
/* Read and write starters can change their formats. */
(* informat.h->startread)(&informat);
checkformat(&informat);
report("Input file: using sample rate %u\n\tsize %s, style %s, %d %s",
informat.info.rate, sizes[informat.info.size],
styles[informat.info.style], informat.info.channels,
(informat.info.channels > 1) ? "channels" : "channel");
if (informat.comment)
report("Input file: comment \"%s\"\n",
informat.comment);
if (writing) {
copyformat(&informat, &outformat);
(* outformat.h->startwrite)(&outformat);
checkformat(&outformat);
cmpformats(&informat, &outformat);
report("Output file: using sample rate %u\n\tsize %s, style %s, %d %s",
outformat.info.rate, sizes[outformat.info.size],
styles[outformat.info.style], outformat.info.channels,
(outformat.info.channels > 1) ? "channels" : "channel");
if (outformat.comment)
report("Output file: comment \"%s\"\n",
outformat.comment);
}
/* Very Important:
* Effect fabrication and start is called AFTER files open.
* Effect may write out data beforehand, and
* some formats don't know their sample rate until now.
*/
checkeffect(0);
/* inform effect about signal information */
eff.ininfo = informat.info;
eff.outinfo = outformat.info;
(* eff.h->start)(&eff);
istart = 0;
while((isamp = (*informat.h->read)(&informat,&ibuf[istart],
(long) BUFSIZ-istart))>0) {
long *ib = ibuf;
isamp += istart;
/* Do volume before effect or after? idunno */
if (dovolume) for (i = 0; i < isamp; i++)
ibuf[i] = volumechange(ibuf[i]);
osamp = sizeof(obuf) / sizeof(long);
/* Effect (i.e. rate change) may do different sizes I and O */
while (isamp) {
idone = isamp;
odone = osamp;
(* eff.h->flow)(&eff, ib, obuf, &idone, &odone);
/*
* kludge!
* Effect is stuck. Start over with new buffer.
* This can drop samples at end of file.
* No effects currently do this, but it could happen.
*/
if (idone == 0) {
int i;
for(i = isamp - 1; i; i--)
ibuf[i] = ib[i];
istart = isamp;
isamp = 0;
break;
}
if (writing && (odone > 0))
(* outformat.h->write)(&outformat, obuf, (long) odone);
isamp -= idone;
ib += idone;
}
}
/* Drain effect out */
if (writing) {
odone = sizeof(obuf) / sizeof(long);
(* eff.h->drain)(&eff, obuf, &odone);
if (odone > 0)
(* outformat.h->write)(&outformat, obuf, (long) odone);
/* keep calling it until it returns a partial buffer */
while (odone == (sizeof(obuf) / sizeof(long))) {
(* eff.h->drain)(&eff, obuf, &odone);
if (odone)
(* outformat.h->write)(&outformat, obuf, (long) odone);
}
}
/* Very Important:
* Effect stop is called BEFORE files close.
* Effect may write out more data after.
*/
(* eff.h->stop)(&eff);
(* informat.h->stopread)(&informat);
fclose(informat.fp);
if (writing)
(* outformat.h->stopwrite)(&outformat);
if (writing)
fclose(outformat.fp);
}
/*
* Check that we have a known format suffix string.
*/
void
gettype(formp)
ft_t formp;
{
char **list;
int i;
if (! formp->filetype)
fail("Must give file type for %s file, either as suffix or with -t option",
formp->filename);
for(i = 0; formats[i].names; i++) {
for(list = formats[i].names; *list; list++) {
char *s1 = *list, *s2 = formp->filetype;
if (! strcmpcase(s1, s2))
break; /* not a match */
}
if (! *list)
continue;
/* Found it! */
formp->h = &formats[i];
return;
}
if (! strcmpcase(formp->filetype, "snd")) {
verbose = 1;
report("File type '%s' is used to name several different formats.", formp->filetype);
report("If the file came from a Macintosh, it is probably");
report("a .ub file with a sample rate of 11025 (or possibly 5012 or 22050).");
report("Use the sequence '-t .ub -r 11025 file.snd'");
report("If it came from a PC, it's probably a Soundtool file.");
report("Use the sequence '-t .sndt file.snd'");
report("If it came from a NeXT, it's probably a .au file.");
fail("Use the sequence '-t .au file.snd'\n");
}
fail("File type '%s' of %s file is not known!",
formp->filetype, formp->filename);
}
copyformat(ft, ft2)
ft_t ft, ft2;
{
int noise = 0;
if (ft2->info.rate == 0) {
ft2->info.rate = ft->info.rate;
noise = 1;
}
if (outformat.info.size == -1) {
ft2->info.size = ft->info.size;
noise = 1;
}
if (outformat.info.style == -1) {
ft2->info.style = ft->info.style;
noise = 1;
}
if (outformat.info.channels == -1) {
ft2->info.channels = ft->info.channels;
noise = 1;
}
if (outformat.comment == NULL) {
ft2->comment = ft->comment;
noise = 1;
}
return noise;
}
cmpformats(ft, ft2)
ft_t ft, ft2;
{
int noise = 0;
float abs;
}
/* check that all settings have been given */
checkformat(ft)
ft_t ft;
{
if (ft->info.rate == 0)
fail("Sampling rate for %s file was not given\n", ft->filename);
if ((ft->info.rate < 100) || (ft->info.rate > 50000))
fail("Sampling rate %u for %s file is bogus\n",
ft->info.rate, ft->filename);
if (ft->info.size == -1)
fail("Data size was not given for %s file\nUse one of -b/-w/-l/-f/-d/-D", ft->filename);
if (ft->info.style == -1)
fail("Data style was not given for %s file\nUse one of -s/-u/-U/-A", ft->filename);
/* it's so common, might as well default */
if (ft->info.channels == -1)
ft->info.channels = 1;
/* fail("Number of output channels was not given for %s file",
ft->filename); */
}
/*
* If no effect given, decide what it should be.
*/
void
checkeffect(effp)
eff_t effp;
{
int already = (eff.name != (char *) 0);
char *rate = 0, *chan = 0;
int i;
for (i = 0; effects[i].name; i++) {
if (!chan && (effects[i].flags & EFF_CHAN))
chan = effects[i].name;
if (! rate && (effects[i].flags & EFF_RATE))
rate = effects[i].name;
}
if (eff.name && ! writing)
return;
/*
* Require mixdown for channel mismatch.
* XXX Doesn't handle channel expansion. Need an effect for this.
* Require one of the rate-changers on a rate change.
* Override a rate change by explicitly giving 'copy' command.
*/
if (informat.info.channels != outformat.info.channels) {
if (eff.name && !(eff.h->flags & EFF_CHAN))
fail("Need to do change number of channels first. Try the '%s' effect.", chan);
if (! eff.name) {
eff.name = chan;
report(
"Changing %d input channels to %d output channels with '%s' effect\n",
informat.info.channels, outformat.info.channels, chan);
geteffect(&eff);
}
}
/*
* Be liberal on rate difference errors.
* Note that the SPARC 8000-8192 problem
* comes in just under the wire. XXX
*
* Bogus. Should just do a percentage.
*/
if (abs(informat.info.rate - outformat.info.rate) > 200) {
if (eff.name && !(eff.h->flags & EFF_RATE))
fail("Need to do rate change first. Try the '%s' effect.",
rate);
if (! eff.name) {
eff.name = rate;
report(
"Changing sample rate %u to rate %u via noisy 'rate' effect\n",
informat.info.rate, outformat.info.rate);
geteffect(&eff);
}
}
/* don't need to change anything */
if (! eff.name)
eff.name = "copy";
if (! already) {
geteffect(&eff);
/* give default opts for manufactured effect */
(* eff.h->getopts)(&eff, 0, (char *) 0);
}
}
/*
* Check that we have a known effect name.
*/
void
geteffect(effp)
eff_t effp;
{
int i;
for(i = 0; effects[i].name; i++) {
char *s1 = effects[i].name, *s2 = effp->name;
while(*s1 && *s2 && (tolower(*s1) == tolower(*s2)))
s1++, s2++;
if (*s1 || *s2)
continue; /* not a match */
/* Found it! */
effp->h = &effects[i];
return;
}
/* Guido Van Rossum fix */
fprintf(stderr, "Known effects:");
for (i = 0; effects[i].name; i++)
fprintf(stderr, "\t%s\n", effects[i].name);
fail("\nEffect '%s' is not known!", effp->name);
}
/* Guido Van Rossum fix */
statistics() {
if (dovolume && clipped > 0)
report("Volume change clipped %d samples", clipped);
}
long volumechange(y)
long y;
{
double y1;
y1 = y * volume;
if (y1 < -2147483647.0) {
y1 = -2147483647.0;
clipped++;
}
else if (y1 > 2147483647.0) {
y1 = 2147483647.0;
clipped++;
}
return y1;
}
#if defined(unix) || defined(AMIGA)
filetype(fd)
int fd;
{
struct stat st;
fstat(fd, &st);
return st.st_mode & S_IFMT;
}
#endif
char *usagestr =
"[ -V -S ] [ fopts ] ifile [ fopts ] ofile [ effect [ effopts ] ]\nfopts: -r rate -v volume -c channels -s/-u/-U/-A -b/-w/-l/-f/-d/-D -x\neffects and effopts: various";
usage(opt)
char *opt;
{
#ifndef DOS
/* single-threaded machines don't really need this */
fprintf(stderr, "%s: ", myname);
#endif
fprintf(stderr, "Usage: %s", usagestr);
if (opt)
fprintf(stderr, "\nFailed at: %s\n", opt);
exit(1);
}
void
#ifdef __STDC__
report(char *fmt, ...)
#else
report(va_alist)
va_dcl
#endif
{
va_list args;
#ifndef __STDC__
char *fmt;
#endif
if (! verbose)
return;
#ifndef DOS
/* single-threaded machines don't really need this */
fprintf(stderr, "%s: ", myname);
#endif
#ifdef __STDC__
va_start(args, fmt);
#else
va_start(args);
fmt = va_arg(args, char *);
#endif
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
void
#ifdef __STDC__
fail(char *fmt, ...)
#else
fail(va_alist)
va_dcl
#endif
{
va_list args;
#ifndef __STDC__
char *fmt;
#endif
#ifndef DOS
/* single-threaded machines don't really need this */
fprintf(stderr, "%s: ", myname);
#endif
#ifdef __STDC__
va_start(args, fmt);
#else
va_start(args);
fmt = va_arg(args, char *);
#endif
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
exit(2);
}
strcmpcase(s1, s2)
char *s1, *s2;
{
while(*s1 && *s2 && (tolower(*s1) == tolower(*s2)))
s1++, s2++;
return *s1 - *s2;
}